﻿// Copyright © Microsoft Corporation.  All Rights Reserved.
// This code released under the terms of the 
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)

namespace SketchFlow.SaveState.Interactions
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Controls.Primitives;
	using System.Windows.Interactivity;
	using Microsoft.Expression.Prototyping.Navigation;
	using Microsoft.Expression.Prototyping.Services;

	[TypeConstraint(typeof(FrameworkElement))]
	public sealed class SaveScreenStateBehavior : Behavior<FrameworkElement>
	{
		private static readonly Dictionary<string, object> dataStore = new Dictionary<string, object>();

		private static readonly PreservePropertyInfo[] propertiesToPreserve = new PreservePropertyInfo[]
		{
			new PreservePropertyInfo(typeof(Calendar), Calendar.DisplayDateProperty, "DisplayDate"),
			new PreservePropertyInfo(typeof(Calendar), Calendar.SelectedDateProperty, "SelectedDate"),
			new PreservePropertyInfo(typeof(ComboBox), ComboBox.SelectedIndexProperty, "SelectedIndex"),
			new PreservePropertyInfo(typeof(ComboBoxItem), ComboBoxItem.IsSelectedProperty, "IsSelected"),
			new PreservePropertyInfo(typeof(DatePicker), DatePicker.SelectedDateProperty, "SelectedDate"),
			new PreservePropertyInfo(typeof(ListBox), ListBox.SelectedIndexProperty, "SelectedIndex"),
			new PreservePropertyInfo(typeof(ListBoxItem), ListBoxItem.IsSelectedProperty, "IsSelected"),
			new PreservePropertyInfo(typeof(ScrollBar), ScrollBar.ValueProperty, "Value"),
			new PreservePropertyInfo(typeof(Slider), Slider.ValueProperty, "Value"),
			new PreservePropertyInfo(typeof(TextBox), TextBox.TextProperty, "Text"),
			new PreservePropertyInfo(typeof(TabControl), TabControl.SelectedIndexProperty, "SelectedIndex"),
			new PreservePropertyInfo(typeof(TreeView), TreeView.SelectedValuePathProperty, "SelectedValuePath"),
			new PreservePropertyInfo(typeof(TreeViewItem), TreeViewItem.IsExpandedProperty, "IsExpanded"),
			new PreservePropertyInfo(typeof(ToggleButton), ToggleButton.IsCheckedProperty, "IsChecked"),
			new PreservePropertyInfo(typeof(TreeViewItem), TreeViewItem.IsExpandedProperty, "IsExpanded"),
			new PreservePropertyInfo(typeof(TreeViewItem), TreeViewItem.IsSelectedProperty, "IsSelected"),
		};

		protected override void OnAttached()
		{
			base.OnAttached();
			this.AttachCore();
		}

		protected override void OnDetaching()
		{
			base.OnDetaching();
			this.DetachCore();
		}

		private void AssociatedObject_LayoutUpdated(object sender, EventArgs e)
		{
			this.AssociatedObject.LayoutUpdated -= this.AssociatedObject_LayoutUpdated;
			this.PreserveStateVisitor(this.LoadState, this.AssociatedObject);
		}

		private void AssociatedObject_Unloaded(object sender, EventArgs e)
		{
			this.DetachCore();
		}

		private void ActiveNavigationViewModel_CurrentScreenChanging(object sender, EventArgs e)
		{
			this.PreserveStateVisitor(this.SaveState, this.AssociatedObject);
		}

		private void PreserveStateVisitor(Action<FrameworkElement, DependencyProperty, string> preserveStateAction, DependencyObject parent)
		{
			FrameworkElement parentElement = parent as FrameworkElement;
			if (parentElement != null)
			{
				foreach (PreservePropertyInfo target in propertiesToPreserve
					.Where(target => target.Type.IsAssignableFrom(parent.GetType())))
				{
					preserveStateAction(parentElement, target.Property, target.PropertyName);
				}
			}

			foreach (DependencyObject child in DependencyObjectHelper.GetVisualChildren(parent))
			{
				PreserveStateVisitor(preserveStateAction, child);
			}
		}

		private void SaveState(FrameworkElement element, DependencyProperty property, string propertyName)
		{
			if (HasBindingExpression(element, property))
			{
				return;
			}

			string key = DependencyObjectHelper.GeneratePathString(element, this.GetType().FullName, element.GetType().FullName + "/" + propertyName);
			if (string.IsNullOrEmpty(key))
			{
				return;
			}

			dataStore[key] = element.GetValue(property);
		}

		private void LoadState(FrameworkElement element, DependencyProperty property, string propertyName)
		{
			if (HasBindingExpression(element, property))
			{
				return;
			}

			string key = DependencyObjectHelper.GeneratePathString(element, this.GetType().FullName, element.GetType().FullName + "/" + propertyName);
			if (string.IsNullOrEmpty(key))
			{
				return;
			}

			if (!dataStore.ContainsKey(key))
			{
				return;
			}

			element.SetValue(property, dataStore[key]);
		}

		private void AttachCore()
		{
			this.AssociatedObject.LayoutUpdated += this.AssociatedObject_LayoutUpdated;
			this.AssociatedObject.Unloaded += this.AssociatedObject_Unloaded;
			PlayerContext.Instance.ActiveNavigationViewModel.CurrentScreenChanging += this.ActiveNavigationViewModel_CurrentScreenChanging;
		}

		private void DetachCore()
		{
			this.AssociatedObject.LayoutUpdated -= this.AssociatedObject_LayoutUpdated;
			this.AssociatedObject.Unloaded -= this.AssociatedObject_Unloaded;
			PlayerContext.Instance.ActiveNavigationViewModel.CurrentScreenChanging -= this.ActiveNavigationViewModel_CurrentScreenChanging;
		}

		private static bool HasBindingExpression(FrameworkElement element, DependencyProperty property)
		{
			return element.GetBindingExpression(property) != null;
		}

		private class PreservePropertyInfo
		{
			internal PreservePropertyInfo(Type type, DependencyProperty property, string propertyName)
			{
				this.Type = type;
				this.Property = property;
				this.PropertyName = propertyName;
			}

			internal Type Type { get; private set; }
			internal DependencyProperty Property { get; private set; }
			internal string PropertyName { get; private set; }
		}
	}
}

